package com.zmc.lostfound.file.service.impl;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zmc.lostfound.common.exception.MyException;
import com.zmc.lostfound.common.utils.Result;
import com.zmc.lostfound.file.dao.FileDao;
import com.zmc.lostfound.file.entity.FileEntity;
import com.zmc.lostfound.file.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author ZMC
 * @description 文件管理业务接口
 * @email 2869581855@qq.com
 * @date 2023-07-05 15:30:10
 */
@Slf4j
@Service
public class FileServiceImpl extends ServiceImpl<FileDao, FileEntity> implements FileService {

    /**
     * accessKey
     */
    @Value("${alibaba.cloud.access-key}")
    private String accessKey;

    /**
     * 地域节点
     */
    @Value("${alibaba.cloud.oss.endpoint}")
    private String endpoint;

    /**
     * bucket名称
     */
    @Value("${alibaba.cloud.oss.bucket}")
    private String bucketName;

    @Resource
    private OSSClient ossClient;

    /**
     * 获取文件上传签名
     *
     * @return Result
     */
    @Override
    public Result getPolicy() {
        // 填写Host地址
        String host = "https://" + bucketName + "." + endpoint;

        // 设置上传回调URL，即回调服务器地址，用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后，把文件上传信息通过此回调URL发送给应用服务器。
        //String callbackUrl = "https://192.168.0.0:8888";

        // 设置上传到OSS文件的前缀，可置空此项。置空后，文件将上传至Bucket的根目录下。
        String directoryName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));

        Map<String, String> respMap;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, directoryName);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<>();
            respMap.put("accessKey", accessKey);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("directoryName", directoryName);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
        } catch (Exception e) {
            log.error(e.getMessage());
            return Result.error("文件上传签名获取失败").put("data", null);
        }
        log.info("文件上传签名获取成功");
        return Result.ok("文件上传签名获取成功").put("data", respMap);
    }

    /**
     * 上传图片或文档
     *
     * @param file 文件
     * @return Result
     */
    @Override
    public Result upLoadFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            return Result.error("文件不能为空");
        }
        // 设置上传到OSS文件的前缀，可置空此项。置空后，文件将上传至Bucket的根目录下
        String directoryName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));
        // 填写Object完整路径，完整路径中不能包含Bucket名称，例如exampledir/exampleobject.txt
        String objectName = directoryName + UUID.randomUUID() + "_" + file.getOriginalFilename();
        // 填写本地文件的完整路径，例如D:\\localpath\\examplefile.txt
        String localFilePath;
        try {
            // 创建临时文件
            File temporaryFile = File.createTempFile("oss", "temporary");
            // 将上传的文件拷贝到临时文件
            file.transferTo(temporaryFile);
            // 获取localFilePath
            localFilePath = temporaryFile.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error("上传失败: 获取文件本地路径失败").put("IOException", e);
        }
        // 在内存创建虚拟文件(new File不会创建真实文件)
        File temporaryFile = new File(localFilePath);
        if (!temporaryFile.exists()) {
            MyException.throwException("临时文件创建失败");
        }
        // 获取文件的MD5加密串
        String fileMd5 = this.getFileMd5(temporaryFile);
        // 判断该文件是否已经上传到OSS
        String imgUrl = this.checkFile(fileMd5);
        if (imgUrl != null) {
            return Result.ok("该文件已经上传到OSS").put("imgUrl", imgUrl);
        }
        // 上传文件
        ossClient.putObject(bucketName, objectName, temporaryFile);
        // Host地址
        String host = "https://" + bucketName + "." + endpoint;
        // 文件url
        String url = host + "/" + objectName;
        // 将该文件信息插入数据库，避免之后重复上传该文件
        this.save(FileEntity.builder()
                .id(fileMd5)
                .type(0)
                .fileName(file.getOriginalFilename())
                .fileSize(file.getSize())
                .fileUrl(url)
                .bucket(bucketName)
                .filePath(objectName)
                .build());
        return Result.ok("上传成功").put("imgUrl", url);
    }

    /**
     * 判断该文件是否已经上传到OSS
     *
     * @param fileMd5 文件的MD5加密串
     * @return 已上传:返回imgUrl，未上传:null
     */
    private String checkFile(String fileMd5) {
        // 查询该文件是否存在于数据库
        FileEntity fileEntity = this.getById(fileMd5);
        // 该文件已上传到OSS，不用重复上传
        if (fileEntity != null) {
            return fileEntity.getFileUrl();
        }
        return null;
    }

    /**
     * @param file 文件
     * @return 文件的MD5加密串
     * @description 获取文件的MD5加密串
     */
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            return DigestUtils.md5Hex(fileInputStream);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}